from django.db.models import signals, get_app
from django.dispatch  import dispatcher
from utils            import create_history_model
from .                import CHANGE_TYPES, history_model_cache, log

#try:
#    from ..threadlocals.middleware import get_current_user_and_ip
#except ImportError:
get_current_user_and_ip = None

"""
    The ModelHistories object is the object that does all the work. When
    constructed it registers signal handlers for all history enabled models.
    The signal handlers update the corresponding history tables.
"""


class ModelHistories(object):
    def __init__(self):
        self.model_object_cache = dict()
        for model in history_model_cache:
            dispatcher.connect(self.save_revision, signal=signals.post_save, sender=model)
            dispatcher.connect(self.record_deleted, signal=signals.pre_delete, sender=model)


    def get_or_create_history_model(self, model):
        """
            Checks the history class cache for history class definition for
            given model. If it does not exist it creates the history class and
            adds it to the cache.
        """
        if model in self.model_object_cache:
            return self.model_object_cache[model]
        else:
            self.model_object_cache[model] = create_history_model(model)
            return self.model_object_cache[model]

        
    def save_revision(self, sender, instance):
        """
            Wraps update_history and calls it with 'Update' revision type.
        """
        return self.update_history(sender=sender, instance=instance, update_type='U')

    
    def record_deleted(self, sender, instance):
        """
            Wraps update_history and calls it with 'Delete' revision type.
        """
        return self.update_history(sender=sender, instance=instance, update_type='D')


    def update_history(self, sender, instance, update_type):
        """
            Does all the work of adding entries in the history log for the given
            instance of a django model class.
        """
        
        history_model = self.get_or_create_history_model(sender)

        num_revisions = history_model.objects.filter(**{instance._meta.pk.attname: instance.__dict__[instance._meta.pk.attname]}).count()
        new_revision  = history_model()
        
        for field in instance._meta.fields:
            new_revision.__dict__[field.attname] = instance.__dict__[field.attname]

        if not get_current_user_and_ip:
            new_revision.__dict__['hst_modified_by_user'] = 0
        else:
            try:
                new_revision.__dict__['hst_modified_by_user'] = get_current_user_and_ip()['user'].id
                new_revision.__dict__['hst_modified_from_ip'] = get_current_user_and_ip()['ip']
            except AttributeError:
                new_revision.__dict__['hst_modified_by_user'] = 0


        new_revision.__dict__['hst_revision'] = num_revisions + 1

        if num_revisions == 0:
            new_revision.__dict__['hst_revision_type'] = 'A'
        else:
            new_revision.__dict__['hst_revision_type'] = update_type
            
        try:
            new_revision.save()
        except Exception, e:
            log("Error saving history: %s" % str(e))


    def get_num_revisions(self, instance):
        """
            Returns number of history records for given model instance.
        """
        if instance.__class__ not in history_model_cache:
            raise Exception("Not a history enabled class.")
        else:
            history_model = self.get_or_create_history_model(instance.__class__)
            return history_model.objects.filter(**{instance._meta.pk.attname: instance.__dict__[instance._meta.pk.attname]}).count()


    def get_revision(self, instance, revision=None):
        """
            Returns instance of instance's corresponding history class which matches
            the revision number supplied. If no revision numbers supplied, returns
            last revision.
        """
        if instance.__class__ not in history_model_cache:
            raise Exception("Not a history enabled class.")
        else:
            history_model = self.get_or_create_history_model(instance.__class__)
            if not revision:
                latest_rev_num = history_model.objects.filter(**{instance._meta.pk.attname: instance.__dict__[instance._meta.pk.attname]}).count()
                if latest_rev_num > 1:
                    return history_model.objects.filter(**{instance._meta.pk.attname: instance.__dict__[instance._meta.pk.attname]}).get(hst_revision=latest_rev_num-1)
                else:
                    raise Exception("No history for given model instance.")
            else:
                try:
                    return history_model.objects.filter(**{instance._meta.pk.attname: instance.__dict__[instance._meta.pk.attname]}).get(hst_revision=int(revision))
                except Exception:
                    raise Exception("Revision does not exist.")


    def get_history(self, instance):
        """
            Returns list of all history records for given model instance.
        """
        if instance.__class__ not in history_model_cache:
            raise Exception("Not a history enabled class.")
        else:
            history_model = self.get_or_create_history_model(instance.__class__)
            return history_model.objects.filter(**{instance._meta.pk.attname: instance.__dict__[instance._meta.pk.attname]})

    def get_deleted_history(self, model_class, pk):
        """
            Returns list of all history records for a deleted model with the
            given model class and primary key value.
        """
        if model_class not in history_model_cache:
            raise Exception("Not a history enabled class.")
        else:
            history_model = self.get_or_create_history_model(instance.__class__)
            return history_model.objects.filter(**{model_class._meta.pk.attname: pk})
        


# 'global' object which handles all signals and updates for history enabled models
model_histories = ModelHistories()